package dk.aiszone.lang.html.autosource;

import java.util.HashMap;
import java.util.Map;

import dk.aiszone.lang.autosource.generatorobjects.Annotation;
import dk.aiszone.lang.autosource.generatorobjects.Argument;
import dk.aiszone.lang.autosource.generatorobjects.Class;
import dk.aiszone.lang.autosource.generatorobjects.ClassBody;
import dk.aiszone.lang.autosource.generatorobjects.Declaration;
import dk.aiszone.lang.autosource.generatorobjects.GenericClass;
import dk.aiszone.lang.autosource.generatorobjects.Import;
import dk.aiszone.lang.autosource.generatorobjects.JavaDoc;
import dk.aiszone.lang.autosource.generatorobjects.JavaFile;
import dk.aiszone.lang.autosource.generatorobjects.Method;
import dk.aiszone.lang.autosource.generatorobjects.Scope;
import dk.aiszone.lang.autosource.generatorobjects.Variable;
import dk.aiszone.lang.autosource.generatorobjects.helpers.ImportsList;
import dk.aiszone.lang.html.HTMLDomainModelGenerator.Attribute;
import dk.aiszone.lang.html.HTMLDomainModelGenerator.Restriction;

/**
 * Generator class containing the source for writing a HTML element file.
 * 
 * @author Ais
 */
public class ElementClass extends JavaFile {

    private static final Map<String, String> ATTRIBUTES_CONVERSIONS = new HashMap<String, String>();
    static {
        ATTRIBUTES_CONVERSIONS.put("accept-charset", "acceptCharset");
        ATTRIBUTES_CONVERSIONS.put("char", "_char");
        ATTRIBUTES_CONVERSIONS.put("class", "_class");
        ATTRIBUTES_CONVERSIONS.put("for", "_for");
        ATTRIBUTES_CONVERSIONS.put("html", "Page");
        ATTRIBUTES_CONVERSIONS.put("http-equiv", "httpEquiv");
        ATTRIBUTES_CONVERSIONS.put("onabort", "onAbort");
        ATTRIBUTES_CONVERSIONS.put("onafterupdate", "onAfterUpdate");
        ATTRIBUTES_CONVERSIONS.put("onbeforeunload", "onBeforeUnload");
        ATTRIBUTES_CONVERSIONS.put("onbeforeupdate", "onBeforeUpdate");
        ATTRIBUTES_CONVERSIONS.put("onblur", "onBlur");
        ATTRIBUTES_CONVERSIONS.put("onbounce", "onBounce");
        ATTRIBUTES_CONVERSIONS.put("onchange", "onChange");
        ATTRIBUTES_CONVERSIONS.put("onclick", "onClick");
        ATTRIBUTES_CONVERSIONS.put("ondataavailable", "onDataAvailable");
        ATTRIBUTES_CONVERSIONS.put("ondatasetchanged", "onDataSetChanged");
        ATTRIBUTES_CONVERSIONS.put("ondatasetcomplete", "onDataSetComplete");
        ATTRIBUTES_CONVERSIONS.put("ondblclick", "onDblClick");
        ATTRIBUTES_CONVERSIONS.put("ondragdrop", "onDragDrop");
        ATTRIBUTES_CONVERSIONS.put("ondragstart", "onDragStart");
        ATTRIBUTES_CONVERSIONS.put("onerror", "onError");
        ATTRIBUTES_CONVERSIONS.put("onerrorupdate", "onErrorUpdate");
        ATTRIBUTES_CONVERSIONS.put("onfilterchange", "onFilterChange");
        ATTRIBUTES_CONVERSIONS.put("onfinish", "onFinish");
        ATTRIBUTES_CONVERSIONS.put("onfocus", "onFocus");
        ATTRIBUTES_CONVERSIONS.put("onhelp", "onHelp");
        ATTRIBUTES_CONVERSIONS.put("onkeydown", "onKeyDown");
        ATTRIBUTES_CONVERSIONS.put("onkeypress", "onKeyPress");
        ATTRIBUTES_CONVERSIONS.put("onkeyup", "onKeyUp");
        ATTRIBUTES_CONVERSIONS.put("onload", "onLoad");
        ATTRIBUTES_CONVERSIONS.put("onmousedown", "onMouseDown");
        ATTRIBUTES_CONVERSIONS.put("onmousemove", "onMouseMove");
        ATTRIBUTES_CONVERSIONS.put("onmouseout", "onMouseOut");
        ATTRIBUTES_CONVERSIONS.put("onmouseover", "onMouseOver");
        ATTRIBUTES_CONVERSIONS.put("onmouseup", "onMouseUp");
        ATTRIBUTES_CONVERSIONS.put("onmove", "onMove");
        ATTRIBUTES_CONVERSIONS.put("onreadystatechanged", "onReadyStateChanged");
        ATTRIBUTES_CONVERSIONS.put("onreset", "onReset");
        ATTRIBUTES_CONVERSIONS.put("onresize", "onResize");
        ATTRIBUTES_CONVERSIONS.put("onrowenter", "onRowEnter");
        ATTRIBUTES_CONVERSIONS.put("onrowexit", "onRowExit");
        ATTRIBUTES_CONVERSIONS.put("onscroll", "onScroll");
        ATTRIBUTES_CONVERSIONS.put("onselect", "onSelect");
        ATTRIBUTES_CONVERSIONS.put("onselectstart", "onSelectStart");
        ATTRIBUTES_CONVERSIONS.put("onstart", "onStart");
        ATTRIBUTES_CONVERSIONS.put("onsubmit", "onSubmit");
        ATTRIBUTES_CONVERSIONS.put("onunload", "onUnload");
    }

    private final ClassBody body;
    private final Method toHtml;

    /**
     * Constructor for the <code>ElementClass</code>.
     * 
     * @param _package
     *            The package of the class.
     * @param name
     *            The name of the enum class.
     */
    public ElementClass(String _package, String name) {
        setPackageName(_package);

        setBody(body = new ClassBody(new Declaration(Scope.PUBLIC, true, false, false), capatialize(name)));
        body.addImplements(new Class("HtmlElement"));

        JavaDoc javadoc = new JavaDoc();
        javadoc.setDescription("Class representing the " + name + " html tag.");
        body.setJavaDoc(javadoc);

        Variable elements = new Variable(new Declaration(Scope.PRIVATE), new Class("java.util.List").addGeneric(new GenericClass(new Class("HtmlElement"))), "elements");
        elements.setInitialization("new java.util.ArrayList<HtmlElement>();");
        body.addVariable(elements);

        toHtml = new Method(new Declaration(Scope.PUBLIC), new Class("String"), "toHtml");
        toHtml.appendToBody("StringBuilder sb = new StringBuilder();\n");
        toHtml.addAnnotation(new Annotation(new Class("Override")));

        toHtml.addNeededImport(new Import("java.util.Iterator"));

        toHtml.appendToBodyEnd("for (Iterator<HtmlElement> iterator = elements.iterator(); iterator.hasNext();) {\n");
        toHtml.appendToBodyEnd("sb.append(iterator.next().toHtml());\n");
        toHtml.appendToBodyEnd("}\n");
        toHtml.appendToBodyEnd("return sb.toString();");

        body.addMethod(toHtml);
    }

    /**
     * Add an attribute to the <code>ElementClass</code>.
     * 
     * @param attribute
     *            <code>Attribute</code> containing information about the attribute.
     */
    public void addAttribute(Attribute attribute) {
        String name = attribute.name;
        if (ATTRIBUTES_CONVERSIONS.containsKey(name)) {
            name = ATTRIBUTES_CONVERSIONS.get(name);
        }

        Class type = new Class(attribute.type._class);
        Restriction restriction = attribute.type.restriction;

        Method set = new Method(new Declaration(Scope.PUBLIC), Class.VOID, "set" + capatialize(name));
        set.addArgument(new Argument(type, name, "The new value of the attribute"));
        set.appendToBody("this." + name + " = " + name + ";");
        if (restriction != null) {
            set.appendToBody(restriction.toJava(name));

            if (restriction.getNeededImports() != null) {
                ImportsList imports = restriction.getNeededImports();
                for (Import _import : imports.getImports()) {
                    set.addNeededImport(_import);
                }
            }
        }

        JavaDoc setDoc = new JavaDoc();
        setDoc.setDescription("Set the value of the <code>" + attribute.name + "</code> attribute.");
        set.setJavaDoc(setDoc);

        body.addMethod(set);

        Variable variable = new Variable(new Declaration(Scope.PRIVATE), type, name);
        body.addVariable(variable);

        if (!type.isPrimitive()) {
            toHtml.appendToBody("if(").appendToBody(name).appendToBody(" != null) {\n");
        }
        toHtml.appendToBody("sb.append(\" ").appendToBody(name).appendToBody(" = \\\"\").append(").appendToBody(name).appendToBody(").append(\"\\\"\");\n");
        if (!type.isPrimitive()) {
            toHtml.appendToBody("}\n");
        }
    }

    /**
     * Add support for adding elements of the specified type to the HTML element.
     * 
     * @param type
     *            The type of element.
     */
    public void addElement(Class type) {
        String attributeName = type.getClassName();

        Method add = new Method(new Declaration(Scope.PUBLIC), Class.VOID, "add" + attributeName);
        add.addArgument(new Argument(type, attributeName, "The <code>" + type.getClassName() + "</code> element to add."));

        JavaDoc javadoc = new JavaDoc();
        javadoc.setDescription("Add a <code>" + type.getClassName() + "</code> element to the <code>" + body.getName() + "</code> object.");
        add.setJavaDoc(javadoc);

        add.appendToBody("if(").appendToBody(attributeName).appendToBody(" != null) {\n");
        add.appendToBody("throw new NullPointerException(\"null element not accepted\");\n");
        add.appendToBody("}\n");
        if (new Class("java.lang.String").equals(type)) {
            add.appendToBody("elements.add(new StringElement(").appendToBody(attributeName).appendToBody("));\n");
        } else {
            add.appendToBody("elements.add(").appendToBody(attributeName).appendToBody(");\n");
        }
        body.addMethod(add);
    }

    private String capatialize(String name) {
        return name.substring(0, 1).toUpperCase() + name.substring(1);
    }
}